package org.axed.user.client;

import java.util.ArrayList;
import com.google.gwt.core.client.GWT;
import com.google.gwt.user.client.Window;

/**
 * A string that handles more complicated issues of the line.
 * It handles anchors, and the correct delivery of parts opening and closing all
 * tags so the snippets are always valid html.
 *
 * an example:
 *
 * text:            "Hello  World." (note the two spaces)
 * range:           Bold, from col 2 to col 4
 * range:           Italic, from col 1 to col 2
 * 
 * indexstring : "H<i>e</i><b>ll</b>o&nbsp; World"
 */

public class LineString implements HtmlStringListener {
	/**
	 * The indexed string for all entries
	 */
	HtmlString str; 

	/**
	 * A list of all anchors in this LineString.
	 */
	ArrayList anchors = new ArrayList();

	/**
	 * TODO
	 */
	ArrayList initAnchors = new ArrayList();
	ArrayList termAnchors = new ArrayList();

	/**
	 * TODO
	 */
	StringBuffer apos = null;
	
	/**
	 * A list of ranges which spawn over this line.
	 * That means, they have neither begin and end in this line, but 
	 * they do spawn over it.
	 */
	ArrayList ranges = new ArrayList();

	/**
	 * When this class inserts/appends entries to str 
	 * it well get an event as answer itself. selfType holds info
	 * for the handler to know what caused the event.
	 */
	private char selfType = ' ';

	/**
	 * Constructor.
	 */
	public LineString(HtmlString str) {
		this.str = str;
		str.addListener(this);
		if (str.idx != null) { // TODO dirty solution.
			apos = new StringBuffer();
		}
	}

	/**
	 * Adds a range that spawns over the LineString.
	 */
	public void addRange(Range range) {
//		if (ranges.indexOf(range) >= 0) {
//			Window.alert("internal error, adding an existent range");
//		}
		ranges.add(range);

		selfType = 'r';
		str.insertEntry(range.getBeginText(), 0);

		selfType = 'r';
		str.appendEntry(range.getBeginText());
	}

	/**
	 * Adds a initatior part of and range-end anchor.
	 */
	public void addInitAnchor(Anchor a) {
		if (initAnchors.indexOf(a) >= 0) {
			return;
		}
		initAnchors.add(a);

		selfType = 'i';
		str.insertEntry(a.parent.getBeginText(), 0);
	}

	/**
	 * Adds a terminator part of and range-end anchor.
	 */
	public void addTermAnchor(Anchor a) {
		if (termAnchors.indexOf(a) >= 0) {
			return;
		}
		termAnchors.add(a);

		selfType = 't';
		str.appendEntry(a.parent.getEndText());
	}

	/**
	 * Appends a column entry at the end.
	 */
	public void appendCol(String s) {
		if (ranges.size() == 0) {
			str.appendEntry(s);
		} else {
			int e = str.entries() - 1;
			str.insertEntry(s, e);
		}
	}

	/**
	 * Appends a normal entry at the end.
	 */
	public void appendCol(char c) {
		appendCol("" + c);
	}

	/**
 	 * Re-sets the htmlText of an anchor.
 	 */
	public void commitAnchor(Anchor a) {
		int e = getEntry(a);
		String s = a.getText();

		if (e < 0) {
			throw new AxedInternalError("Commiting nonexistend anchor.");
		}

		str.replaceEntry(s, e);
	}

	/**
	 * Returns a debug string.
	 */
	public String debug() {
		String s = str.debug();

		s += "A: \"" + apos + "\"";
		return s;
	}

	/**
	 * Gets the entry number of an anchor.
	 */
	public int getEntry(Anchor a) {
		int ai = anchors.indexOf(a);
		if (ai < 0) {
			return -1;
		}
		int e = -1;
		for(int i = 0; i <= ai; i++) {
			e = apos.indexOf("a", e + 1);
		}
		return e;
	}


	public boolean hasRange(Range range) {
		return ranges.indexOf(range) >= 0;
	}

	/**
	 * Inserts a normal entry s at col.
	 */
	public void insertCol(String s, int col) {
		str.insertCol(s, col);
	}

	/**
	 * Inserts an anchor at col.
	 */
	public void insertAnchor(Anchor a, int col) {
		if (anchors.indexOf(a) >= 0 ) {
			// if its already in this line, remove it first.
			removeAnchor(a);
		}

		// insert before the column
		int e = str.entryOf(col);
		selfType = 'a';
		str.insertEntry(a.getText(), e);

		int ax = apos.substring(0, e).replaceAll("[^a]","").length();
		anchors.add(ax, a);
	}

	public void onEntryAppend(HtmlString sender, String s) {
		apos.append(selfType);
		selfType = ' ';
	}

	public void onEntryInsert(HtmlString sender, int entry, String s) {
		apos.insert(entry, selfType);
		selfType = ' ';
	}

	public void onEntryRemove(HtmlString sender, int entry) {
		apos.deleteCharAt(entry);
	}

	public void onEntryReplace(HtmlString sender, int entry, String s) {
		// nothing yet.
	}

	public void onStringClear(HtmlString sender) {
		anchors.clear();
		if (apos != null) {
			apos.delete(0, apos.length());
		}
		ranges.clear();
	}
	
	/**
	 * TODO
	 */
	public void removeInitTerm(Anchor a) {
		int iai = initAnchors.indexOf(a);
		if (iai >= 0) {
			str.removeEntry(0);
			initAnchors.remove(a);
		}

		int tai = termAnchors.indexOf(a);
		if (tai >= 0) {
			int e = str.entries() - 1;
			str.removeEntry(e);
			termAnchors.remove(a);
		}
	}

	/**
	 * Removes an anchor from this line
	 */
	public void removeAnchor(Anchor a) {
		removeInitTerm(a);
		int e = getEntry(a);
		if (e < 0) {
			throw new AxedInternalError("removing nonexistend anchor.");
		}

		str.removeEntry(e);
		anchors.remove(a);
	}

	/**
	 * Removes a range that spawned over this LineString.
	 */
	public void removeRange(Range range) {
		if (!hasRange(range)) {
			// TODO, if Range.commit() would be better, this wouldn't be necessary.
			return;
		}
		int e = str.entries() - 1;
		str.removeEntry(e);
		str.removeEntry(0);
		// TODO handle multiple ranges
		ranges.remove(range);	
	}

	/**
	 * Replaces a column.
	 */
	public void replaceCol(String s, int col) {
		str.replaceCol(s, col);
	}

	/**
 	 * Retrieves the entries from begin column until end column, including non-col entries.
 	 */
	public String retrieveCols(int begin, int end) {
		return str.retrieveCols(begin, end);

/*		String s = str.retrieve(begin, end);
		if (anchors.size() == 0) {
			// if there are no anchors in this line, its simpler
			for(int i = 0; i < ranges.size(); i++) {
				Range r = (Range) ranges.get(i);
				s = r.getBeginText() + s + r.getEndText();
			}
			return s;
		}

		int beginEntry = getEntry(begin);
		int endEntry = getEntry(end);
		// append ranges info
		for(int i = 0; i < anchors.size(); i++) {
			Anchor a = (Anchor) anchors.get(i);

			if (a.isBegin()) {
				Anchor e = a.getOther();
				int beginAnchorEntry = getEntry(a);
				int endAnchorEntry = getEntry(e); 

				if (a.line == e.line) {
					if (beginAnchorEntry < beginEntry) { // starts before this snipped
						if (endAnchorEntry < beginEntry) {
							// starts and stops before this snipped
							continue;
						}
						if (endAnchorEntry < endEntry) {
							// starts before this snipped ends within
							s = a.getText() + s;
							continue;
						}
						// starts before this snipped ends afterward
						s = a.getText() + s + e.getText();
						continue;
					}
					if (beginAnchorEntry < endEntry) { // starts within this snipped
						if (endAnchorEntry < endEntry) {
							// starts and ends within this snipped
							continue;
						}
						// starts within this snipped and ends after
						s = s + e.getText();
						continue;
					}
					// starts and ends after this snipped
					continue;
				}
				// starts in this line ends in a later
				if (beginAnchorEntry < beginEntry) { // starts before this snipped
					s = a.getText() + s + e.getText();
					continue;
				}
				if (beginAnchorEntry > endEntry) { // starts after this snipped
					continue;
				}
				s = s + e.getText();
				continue;
			} else if (a.isEnd()) { // it makes sense to ask if its end, 
			                        // because it can be neither also if the range is not set.
				// its an end anchor, only take care when begin is not within the same line
				Anchor e = a;
				a = e.getOther();
				if (a.line == e.line) {
					continue;
				}
				int endAnchorEntry = getEntry(e); 
				if (endAnchorEntry < beginEntry) {
					// nds left of this snipped
					continue;
				}
				if (endAnchorEntry < endEntry) {
					// end within this snipped
					s = a.getText() + s;
					continue;
				}
				// ends right of this snipped
				s = a.getText() + s + e.getText();
				continue;
			}
		}

		for(int i = 0; i < ranges.size(); i++) {
			Range r = (Range) ranges.get(i);
			s = r.getBeginText() + s + r.getEndText();
		}
		return s;
*/
	}

	/**
 	 * Retrieves the entry e. 
 	 */
	public String retrieveCol(int e) {
		return str.retrieveCol(e);
	}

	/**
 	 * Retrieves the entry e. 
 	 */
	public String retrieveEntry(int e) {
		return str.retrieveEntries(e, e + 1);
	}

	/**
	 * Returns the complete indexed string.
	 */
	public String toString() {
		return str.toString();
	}

	/**
 	 * TODO 
 	 */
	public void onRebuild(HtmlString sender) {
		if (apos != null) {
			return;
		}
		apos = new StringBuffer();
		int e = str.entries();
		for(int i = 0; i < e; i++) {
			apos.append(' ');
		}
	}
}
